frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Tooltip from '@mui/material/Tooltip';
3import IconButton from '@mui/material/IconButton';
4import Box from '@mui/material/Box';
5import Link from '@mui/material/Link';
6import Card from '@mui/material/Card';
7import Container from '@mui/material/Container';
8import TextField from '@mui/material/TextField';
9import Typography from '@mui/material/Typography';
10import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
11import EventIcon from '@mui/icons-material/Event';
12import TuneIcon from '@mui/icons-material/Tune';
13import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
14import {useTheme} from '@mui/material/styles';
15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
16import {PropsWithChildren, useState} from 'react';
17import {useTranslation} from 'react-i18next';
18import pageUtils from '../../../lib/pageUtils';
19import ShareEvent from '../../../containers/ShareEvent';
20import useEventStore from '../../../stores/useEventStore';
21import useToastStore from '../../../stores/useToastStore';
22import useMapStore from '../../../stores/useMapStore';
23import Map from '../../../containers/Map';
24import EventLayout, {TabComponent} from '../../../layouts/Event';
25import {
26 EventByUuidDocument,
27 useUpdateEventMutation,
28} from '../../../generated/graphql';
29import EventPopup from '../../../containers/EventPopup';
30import AddressAutofill from '../../../containers/AddressAutofill';
31
32interface Props {
33 eventUUID: string;
34 announcement?: string;
35}
36
37const Page = (props: PropsWithChildren<Props>) => {
38 return <EventLayout {...props} Tab={DetailsTab} />;
39};
40
41const DetailsTab: TabComponent = ({}) => {
42 const {t} = useTranslation();
43 const theme = useTheme();
44 const {preventUpdateKey, setPreventUpdateKey, setCenter, setMarkers} =
45 useMapStore();
46 const [updateEvent] = useUpdateEventMutation();
47 const addToast = useToastStore(s => s.addToast);
48 const setEventUpdate = useEventStore(s => s.setEventUpdate);
49 const event = useEventStore(s => s.event);
50 const [isEditing, setIsEditing] = useState(false);
51
52 const onSave = async e => {
53 try {
54 const {uuid, ...data} = event;
55 const {id, travels, waitingPassengers, __typename, ...input} = data;
56 await updateEvent({
57 variables: {
58 uuid,
59 eventUpdate: {
60 ...input,
61 },
62 },
63 refetchQueries: ['eventByUUID'],
64 });
65 setIsEditing(false);
66 } catch (error) {
67 console.error(error);
68 addToast(t('event.errors.cant_update'));
69 }
70 };
71
72 const modifyButton = isEditing ? (
73 <Tooltip
74 title={t('event.details.save')}
75 sx={{
76 position: 'absolute',
77 top: theme.spacing(2),
78 right: theme.spacing(2),
79 }}
80 >
81 <IconButton color="primary" onClick={onSave}>
82 <CheckCircleOutlineIcon />
83 </IconButton>
84 </Tooltip>
85 ) : (
86 <Tooltip
87 title={t('event.details.modify')}
88 sx={{
89 position: 'absolute',
90 top: theme.spacing(2),
91 right: theme.spacing(2),
92 }}
93 >
94 <IconButton color="primary" onClick={() => setIsEditing(true)}>
95 <TuneIcon />
96 </IconButton>
97 </Tooltip>
98 );
99
100 if (!event) return null;
101 const {latitude, longitude} = event;
102
103 const mapUpdateKey = `${event.uuid}.details`;
104 if (preventUpdateKey !== mapUpdateKey) {
105 setPreventUpdateKey(mapUpdateKey);
106 setCenter([latitude, longitude]);
107 setMarkers([
108 {
109 double: true,
110 center: [latitude, longitude],
111 popup: <EventPopup event={event} />,
112 },
113 ]);
114 }
115
116 return (
117 <Box
118 sx={{
119 position: 'relative',
120 }}
121 >
122 {latitude && longitude ? <Map /> : <Box pt={6} />}
123 <Container
124 sx={{
125 p: 4,
126 mb: 11,
127 mx: 0,
128 [theme.breakpoints.down('md')]: {
129 p: 2,
130 },
131 }}
132 >
133 <Card
134 sx={{
135 position: 'relative',
136 maxWidth: '100%',
137 width: '350px',
138 p: 2,
139 }}
140 >
141 <Typography variant="h4" pb={2}>
142 {t('event.details')}
143 </Typography>
144 {modifyButton}
145 <Box pt={2} pr={1.5}>
146 <Typography variant="overline">{t('event.fields.name')}</Typography>
147 <Typography variant="body1">
148 {isEditing ? (
149 <TextField
150 size="small"
151 fullWidth
152 value={event.name}
153 onChange={e => setEventUpdate({name: e.target.value})}
154 name="name"
155 id="EditEventName"
156 />
157 ) : (
158 <Typography variant="body1" id="EventName">
159 {event.name ?? t('event.fields.empty')}
160 </Typography>
161 )}
162 </Typography>
163 </Box>
164 <Box pt={2} pr={1.5}>
165 <Typography variant="overline">{t('event.fields.date')}</Typography>
166 {isEditing ? (
167 <Typography variant="body1">
168 <DatePicker
169 slotProps={{
170 textField: {
171 size: 'small',
172 id: `EditEventDate`,
173 fullWidth: true,
174 placeholder: t('event.fields.date_placeholder'),
175 },
176 }}
177 format="DD/MM/YYYY"
178 value={moment(event.date)}
179 onChange={date =>
180 setEventUpdate({
181 date: !date ? null : moment(date).format('YYYY-MM-DD'),
182 })
183 }
184 />
185 </Typography>
186 ) : (
187 <Box position="relative">
188 <Typography variant="body1" id="EventDate">
189 {event.date
190 ? moment(event.date).format('DD/MM/YYYY')
191 : t('event.fields.empty')}
192 </Typography>
193 <EventIcon
194 color="action"
195 sx={{
196 position: 'absolute',
197 right: theme.spacing(-0.5),
198 top: 0,
199 }}
200 />
201 </Box>
202 )}
203 </Box>
204 <Box pt={2} pr={1.5}>
205 <Typography variant="overline">
206 {t('event.fields.address')}
207 </Typography>
208 {isEditing ? (
209 <AddressAutofill
210 label={t('event.creation.address')}
211 address={event.address}
212 onSelect={({location, address}) => {
213 setEventUpdate({
214 address,
215 latitude: location[1],
216 longitude: location[0],
217 });
218 }}
219 />
220 ) : (
221 <Box position="relative">
222 <Typography variant="body1" id="EventAddress" sx={{pr: 3}}>
223 {event.address ? (
224 <Link
225 target="_blank"
226 rel="noreferrer"
227 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
228 event.address
229 )}`}
230 onClick={e => e.preventDefault}
231 >
232 {event.address}
233 </Link>
234 ) : (
235 t('event.fields.empty')
236 )}
237 </Typography>
238 <PlaceOutlinedIcon
239 color="action"
240 sx={{
241 position: 'absolute',
242 right: theme.spacing(-0.5),
243 top: 0,
244 }}
245 />
246 </Box>
247 )}
248 </Box>
249 <Box pt={2} pr={1.5}>
250 <Typography variant="overline">
251 {t('event.fields.description')}
252 </Typography>
253 {isEditing ? (
254 <Typography variant="body1">
255 <TextField
256 fullWidth
257 multiline
258 maxRows={4}
259 inputProps={{maxLength: 250}}
260 value={event.description || ''}
261 onChange={e => setEventUpdate({description: e.target.value})}
262 id={`EditEventDescription`}
263 name="description"
264 />
265 </Typography>
266 ) : (
267 <Typography variant="body1" id="EventDescription" sx={{pr: 3}}>
268 {event.description ?? t('event.fields.empty')}
269 </Typography>
270 )}
271 </Box>
272 {!isEditing && (
273 <ShareEvent
274 title={`Caroster ${event.name}`}
275 sx={{width: '100%', mt: 2}}
276 />
277 )}
278 </Card>
279 </Container>
280 </Box>
281 );
282};
283
284export const getServerSideProps = pageUtils.getServerSideProps(
285 async (context, apolloClient) => {
286 const {uuid} = context.query;
287 const {host = ''} = context.req.headers;
288 let event = null;
289
290 // Fetch event
291 try {
292 const {data} = await apolloClient.query({
293 query: EventByUuidDocument,
294 variables: {uuid},
295 });
296 event = data?.eventByUUID?.data;
297 } catch (error) {
298 return {
299 notFound: true,
300 };
301 }
302
303 return {
304 props: {
305 eventUUID: uuid,
306 metas: {
307 title: event?.attributes?.name || '',
308 url: `https://${host}${context.resolvedUrl}`,
309 },
310 },
311 };
312 }
313);
314export default Page;